package persistence;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javassist.util.proxy.MethodHandler;

import org.apache.commons.beanutils.PropertyUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import persistence.annotation.ManyToOne;
import persistence.annotation.OneToMany;

public class PersistentClassHandler implements MethodHandler {

	/**
	 * 
	 */
	private static Logger log = LoggerFactory.getLogger(PersistentClassHandler.class);

	/**
	 * 
	 */
	private Class<?> clazz = null;

	/**
	 * Session courante
	 */
	private PersistenceSession session = null;

	/**
	 * Liste des méthodes déjà invokées
	 */
	private Set<String> methodsInvoked = new HashSet<String>();

	/*
	 * (non-Javadoc)
	 * 
	 * @see javassist.util.proxy.MethodHandler#invoke(java.lang.Object,
	 * java.lang.reflect.Method, java.lang.reflect.Method, java.lang.Object[])
	 */
	@Override
	public Object invoke(Object bean, Method method, Method proceed, Object[] args) throws Throwable {
		log.debug("PersistentClassHandler invoke sur la méthode [" + method.getName() + "] de la classe [" + method.getReturnType() + "]");

		if (!this.methodsInvoked.contains(method.getName())) {
			this.methodsInvoked.add(method.getName());
			this.session = PersistenceUtil.getConfiguration().getPersistenceSessionFactory().getCurrentSession();
			this.clazz = bean.getClass().getSuperclass();

			// Cas avec méthode annotation OneToMany
			if (method.isAnnotationPresent(OneToMany.class)) {
				log.debug("LAZYLOADING : Intercepte OneToMany de la classe [" + this.clazz + "]");
				this.intercepteOneToMany(bean, method);
			}

			// Cas avec méthode annotation ManyToOne
			if (method.isAnnotationPresent(ManyToOne.class)) {
				log.debug("LAZYLOADING : Intercepte ManyToOne de la classe [" + this.clazz + "]");
				this.intercepteManyToOne(bean, method);
			}
		}

		// Toutes les autres méthodes...
		return proceed.invoke(bean, args);

	}

	/**
	 * LazyLoading : OneToMany
	 * 
	 * @param bean
	 * @param method
	 * @throws Exception
	 */
	private void intercepteOneToMany(Object bean, Method method) throws Exception {

		// Récupération de la classe interceptée
		PersistentClass persistentClass = PersistenceUtil.getConfiguration().getPersistentClass(this.clazz);

		// Récupération de l'id de type ManyToOne (clé étrangère)
		PersistentProperty currentIdPersistentProperty = persistentClass.getId();
		Object id = currentIdPersistentProperty.getValue(bean);

		// On récupère tous les objets de la classe ManyToOne à partir de de
		// l'id de la "clé étrangère"
		PersistentClass persistentClassManyToOne = PersistenceUtil.getConfiguration().getPersistentClass(this.getGenericType(method));
		String requete = "SELECT * FROM " + persistentClassManyToOne.getTableName() + " WHERE " + this.clazz.getSimpleName().toLowerCase() + "=" + id;
		List<?> list = this.session.query(persistentClassManyToOne.getClazz(), requete);

		// On valorise la propriété avec le résultat de la requête
		PropertyUtils.setSimpleProperty(bean, method.getName().substring(3).toLowerCase(), list);

	}

	/**
	 * Récupère la classe générique d'une méthode lors son type utilise la
	 * généricité
	 * 
	 * @param method
	 * @return
	 */
	private Class<?> getGenericType(Method method) {
		ParameterizedType parameterizedType = (ParameterizedType) method.getGenericReturnType();
		return (Class<?>) parameterizedType.getActualTypeArguments()[0];
	}

	/**
	 * LazyLoading : ManyToOne
	 * 
	 * 
	 * 
	 * @param bean
	 * @param method
	 * @throws Exception
	 */
	private void intercepteManyToOne(Object bean, Method method) throws Exception {

		// On récupère la persitentClass correspondant à la propriété ManyToOne
		Class<?> manyToOneClazz = bean.getClass().getSuperclass();
		PersistentClass persistentClass = PersistenceUtil.getConfiguration().getPersistentClass(manyToOneClazz);

		// On modifie le bean
		ManyToOneProperty manyToOneProperty = (ManyToOneProperty) persistentClass.getPersistentProperty(method.getName());
		manyToOneProperty.updateBean(bean, this.session);
	}
}
